home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
pcl
/
cl-nd-cl.lha
/
clue
/
clio
/
buttons.lisp
< prev
next >
Wrap
Text File
|
1992-06-08
|
57KB
|
1,527 lines
;; -*- MODE:LISP; Package:CLIO-OPEN; Base:10; Lowercase:T; Fonts:(CPTFONT); Syntax:Common-Lisp -*-
;;;----------------------------------------------------------------------------------+
;;; |
;;; TEXAS INSTRUMENTS INCORPORATED |
;;; P.O. BOX 149149 |
;;; AUSTIN, TEXAS 78714 |
;;; |
;;; Copyright (C) 1989, 1990 Texas Instruments Incorporated. |
;;; |
;;; Permission is granted to any individual or institution to use, copy, modify, and |
;;; distribute this software, provided that this complete copyright and permission |
;;; notice is maintained, intact, in all copies and supporting documentation. |
;;; |
;;; Texas Instruments Incorporated provides this software "as is" without express or |
;;; implied warranty. |
;;; |
;;;----------------------------------------------------------------------------------+
(in-package "CLIO-OPEN")
(proclaim '(special *default-display-text-font*)) ;; defined in display-text.lisp
(EXPORT '(
action-button
button-font
button-label
button-label-alignment
button-switch
choice-item-highlight-default-p
choice-item-font
choice-item-highlight-selected-p
choice-item-label
choice-item-selected-p
make-action-button
make-action-item
make-toggle-button
action-item
toggle-button
)
'clio-open)
;;; =================================================================================== ;;;
;;; ;;;
;;; C o n s t a n t s a n d S t r u c t u r e s f o r B u t t o n s ;;;
;;; ;;;
;;; =================================================================================== ;;;
(DEFUN create-filled-in-circle-image (circle-image)
(LET ((width (image-width circle-image))
(height (image-height circle-image))
(circle-pixarray (image-z-pixarray circle-image))
filled-in-pixarray next-pixel)
(SETF filled-in-pixarray (make-array `(,height ,width) :element-type 'bit))
(DO ((row 0 (1+ row)) first-1-pixel last-1-pixel)
((= row height))
;; Copy pixels upto the first 1-pixel found scanning from the left...
(DO ((col 0 (1+ col)))
((= col width) ; If no 1-pixels found, claim one was
(SETF first-1-pixel col)) ; found off right-edge of array.
(SETF next-pixel (AREF circle-pixarray row col))
(SETF (AREF filled-in-pixarray row col) next-pixel)
(WHEN (= next-pixel 1)
(SETF first-1-pixel col)
(RETURN)))
;; Copy pixels upto the first 1-pixel found scanning from the right...
(DO ((col (1- width) (1- col)))
((<= col first-1-pixel) ; If no 1-pixels found, use the one
(SETF last-1-pixel col)) ; found in left-to-right scan.
(SETF next-pixel (AREF circle-pixarray row col))
(SETF (AREF filled-in-pixarray row col) next-pixel)
(WHEN (= next-pixel 1)
(SETF last-1-pixel col)
(RETURN)))
;; Fill in the pixels between these two 1-pixels...
(DO ((col (1+ first-1-pixel) (1+ col)))
((>= col last-1-pixel))
(SETF (AREF filled-in-pixarray row col) 1)))
(create-image :width width
:height height
:data filled-in-pixarray)))
(DEFSTRUCT (button-descriptor (:conc-name "") (:type vector))
ab-button-ends-image
ab-clearing-stencil-image
ab-default-ring-image
ab-body-clearing-stencil-image
ab-horizontal-menu-mark-image
ab-vertical-menu-mark-image
ab-height
ab-default-ring-height
ab-left-button-end-width
ab-right-button-end-width
ab-text-baseline ; from top of button.
tb-min-right-margin
ab-menu-mark-bottom-rel-to-baseline ; this includes -1 to compensate for height of
; menu mark.
ab-clearing-stencil-array ; pointer to pixarray of clearing-stencil-image.
ai-default-ring-image
ai-body-clearing-stencil-image
ai-height
ai-default-ring-height
ai-button-end-width
ai-text-baseline
)
;;;
;;; A structure of this type is the value fo the :OL-button-pixmaps property of the display
;;; plist. It is created (if it doesn't already exist for the display) and accessed by the
;;; function get-button-pixmaps.
;;;
(DEFSTRUCT (button-pixmaps (:conc-name ""))
ab-button-ends-pixmap
ab-clearing-stencil-pixmap
ab-default-ring-pixmap
ab-body-clearing-stencil-pixmap
ai-default-ring-pixmap
ai-body-clearing-stencil-pixmap
horizontal-menu-mark-pixmap
vertical-menu-mark-pixmap
)
(DEFPARAMETER *button-dimensions-by-scale*
`(:small
,(make-button-descriptor
:ab-button-ends-image small-action-button-ends
:ab-clearing-stencil-image (create-filled-in-circle-image
small-action-button-ends)
:ab-default-ring-image small-action-button-default-ring
:ab-body-clearing-stencil-image (create-filled-in-circle-image
small-action-button-default-ring)
:ab-height 18
:ab-default-ring-height 13
:ab-left-button-end-width 8
:ab-right-button-end-width 9
:ab-text-baseline 11
:tb-min-right-margin 7
:ab-horizontal-menu-mark-image small-horizontal-menu-mark
:ab-vertical-menu-mark-image small-vertical-menu-mark
:ab-menu-mark-bottom-rel-to-baseline -1
:ai-default-ring-image small-action-item-default-ring
:ai-body-clearing-stencil-image (create-filled-in-circle-image
small-action-item-default-ring)
:ai-height 17
:ai-default-ring-height 16
:ai-button-end-width 8
:ai-text-baseline 10
)
:medium
,(make-button-descriptor
:ab-button-ends-image medium-action-button-ends
:ab-clearing-stencil-image (create-filled-in-circle-image
medium-action-button-ends)
:ab-default-ring-image medium-action-button-default-ring
:ab-body-clearing-stencil-image (create-filled-in-circle-image
medium-action-button-default-ring)
:ab-height 20
:ab-default-ring-height 15
:ab-left-button-end-width 9
:ab-right-button-end-width 10
:ab-text-baseline 12
:tb-min-right-margin 8
:ab-horizontal-menu-mark-image medium-horizontal-menu-mark
:ab-vertical-menu-mark-image medium-vertical-menu-mark
:ab-menu-mark-bottom-rel-to-baseline -1
:ai-default-ring-image medium-action-item-default-ring
:ai-body-clearing-stencil-image (create-filled-in-circle-image
medium-action-item-default-ring)
:ai-height 19
:ai-default-ring-height 18
:ai-button-end-width 9
:ai-text-baseline 13
)
:large
,(make-button-descriptor
:ab-button-ends-image large-action-button-ends
:ab-clearing-stencil-image (create-filled-in-circle-image
large-action-button-ends)
:ab-default-ring-image large-action-button-default-ring
:ab-body-clearing-stencil-image (create-filled-in-circle-image
large-action-button-default-ring)
:ab-height 22
:ab-default-ring-height 17
:ab-left-button-end-width 11
:ab-right-button-end-width 12
:ab-text-baseline 14
:tb-min-right-margin 10
:ab-horizontal-menu-mark-image large-horizontal-menu-mark
:ab-vertical-menu-mark-image large-vertical-menu-mark
:ab-menu-mark-bottom-rel-to-baseline -2
:ai-default-ring-image large-action-item-default-ring
:ai-body-clearing-stencil-image (create-filled-in-circle-image
large-action-item-default-ring)
:ai-height 21
:ai-default-ring-height 20
:ai-button-end-width 10
:ai-text-baseline 13
)
:extra-large
,(make-button-descriptor
:ab-button-ends-image extra-large-action-button-ends
:ab-clearing-stencil-image (create-filled-in-circle-image
extra-large-action-button-ends)
:ab-default-ring-image extra-large-action-button-default-ring
:ab-body-clearing-stencil-image (create-filled-in-circle-image
extra-large-action-button-default-ring)
:ab-height 28
:ab-default-ring-height 23
:ab-left-button-end-width 13
:ab-right-button-end-width 14
:ab-text-baseline 18
:tb-min-right-margin 12
:ab-horizontal-menu-mark-image extra-large-horizontal-menu-mark
:ab-vertical-menu-mark-image extra-large-vertical-menu-mark
:ab-menu-mark-bottom-rel-to-baseline -2
:ai-default-ring-image extra-large-action-item-default-ring
:ai-body-clearing-stencil-image (create-filled-in-circle-image
extra-large-action-item-default-ring)
:ai-height 25
:ai-default-ring-height 23
:ai-button-end-width 14
:ai-text-baseline 16
)))
;;;
;;; Set the clear-stencil-array slot of each button-descriptor...
;;;
(EVAL-WHEN (LOAD eval)
(DOLIST (button-dims *button-dimensions-by-scale*)
(UNLESS (SYMBOLP button-dims) ; skip the property names...
(SETF (ab-clearing-stencil-array button-dims)
(image-z-pixarray (ab-clearing-stencil-image button-dims))))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Label-String |
;;; |
;;;----------------------------------------------------------------------------+
(deftype label-string () 'string)
(defmethod convert (contact value (type (eql 'label-string)))
(declare (ignore contact))
(when (or (symbolp value) (stringp value))
(stringable-label value)))
;;;----------------------------------------------------------------------------+
;;; |
;;; Button |
;;; |
;;;----------------------------------------------------------------------------+
(defcontact button (core contact)
((font :type fontable
:reader button-font ; setf defined below
:initarg :font
:initform *default-display-text-font*)
(label :type (or pixmap label-string)
:reader button-label ; setf defined below
:initarg :label
:initform "")
(label-alignment
:type (member :left :center :right)
:accessor button-label-alignment
:initarg :label-alignment
:initform :left)
(compress-exposures
:initform :off
:type (member :off :on)
:reader contact-compress-exposures
:allocation :class)
(fill-color :type pixel)
(highlight-default-p
:type boolean
:initform nil
:reader choice-item-highlight-default-p) ; setf defined below
;; Selected slot values:
;; 1: unselected
;; 2: selected,
;; -n: select has been pressed, receipt of a release select event
;; will complement the selected state
(selected :type integer
:initform 1)
(last-displayed-as
:type (member :highlighted :unhighlighted)
:initform :unhighlighted)
(preferred-width
:type (or null integer)
:initform nil))
(:resources
label
label-alignment
font))
(DEFGENERIC display-button-highlighted (button &optional x))
(DEFGENERIC display-button-unhighlighted (button &optional x))
;;;----------------------------------------------------------------------------+
;;; |
;;; Accessors |
;;; |
;;;----------------------------------------------------------------------------+
(DEFMETHOD (SETF button-label) (new-label (button button))
(DECLARE (VALUES (OR pixmap string)))
(with-slots (label parent preferred-width width height border-width) button
(let ((converted-label (convert button new-label '(or pixmap label-string))))
(assert converted-label
() "Label ~s is not a stringable, pixmap, or image." new-label)
(setf label converted-label))
(SETF preferred-width NIL) ;Note - This forces recalculation of preferred values.
(if (= 0 width)
;; The *first* time we must initialize geometry
(multiple-value-setq (width height)
(preferred-size button))
;; Otherwise we change-geometry to reflect new size
(when (realized-p button)
;; We defer the change-geometry if button not realized since
;; change-layout will be called when it is realized.
(multiple-value-bind (new-width new-height)
(preferred-size button)
;; We don't invoke change-geometry unless size actually changed.
(unless
(and (= width new-width) (= height new-height))
(change-geometry button :width new-width :height new-height :accept-p t)))))
label))
(defmethod (setf button-font) (new-font (button button))
(declare (values font))
(check-type new-font fontable)
(with-slots (font label) button
(setf font (find-font button new-font))
;; Save original fontname requested. Used again when changing scale.
(setf (getf (window-plist button) 'fontname) new-font)
(when label
(setf (button-label button) label)))
new-font)
(defmethod (setf button-label-alignment) :before (new-alignment (button button))
(check-type new-alignment (member :left :center :right) "(MEMBER :LEFT :CENTER :RIGHT)"))
;;;----------------------------------------------------------------------------+
;;; |
;;; Initialization |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod initialize-instance :after ((button button) &key &allow-other-keys)
(with-slots (label font name fill-color border-width) button
;; Initialize font for current scale
(setf (button-font button) font)
(UNLESS (resource button :name)
(SETF name (stringable-keyword label)))
;; Initialize fill color
(setf fill-color (contact-current-background-pixel button))
(SETF border-width 0)
(MULTIPLE-VALUE-BIND (p-w p-h p-b-w)
(preferred-size button)
(change-geometry button :height p-h :width p-w :border-width p-b-w :accept-p t))))
(defmethod rescale :before ((self button))
;; Find font for new scale, using original fontname requested.
(setf (button-font self) (getf (window-plist self) 'fontname)))
;;; =================================================================================== ;;;
;;; ;;;
;;; C h o i c e P r o t o c o l M e t h o d s ;;;
;;; ;;;
;;; =================================================================================== ;;;
(defmethod (setf choice-item-highlight-default-p) (new-value (button button))
(declare (values new-highlight-default-p-value))
(with-slots (highlight-default-p) button
(let ((new-value (when new-value t)))
(unless (eq new-value highlight-default-p)
(setf highlight-default-p new-value)
(redisplay-button button))))
new-value)
(DEFMETHOD choice-item-font ((button button))
(button-font button))
(DEFMETHOD (SETF choice-item-font) (new-value (button button))
(SETF (button-font button) new-value))
(DEFMETHOD choice-item-label ((button button))
(button-label button))
(defmethod choice-item-highlight-selected-p ((button button))
(declare (values highlight-selected-p))
(with-slots (last-displayed-as) button
(eq last-displayed-as :highlighted)))
(defmethod (setf choice-item-highlight-selected-p) (new-value (button button))
(declare (values highlight-selected-p))
(let ((highlight-selected-p (choice-item-highlight-selected-p button))
(new-value (when new-value t)))
(unless (eq highlight-selected-p new-value)
(if new-value
(display-button-highlighted button)
(display-button-unhighlighted button))))
new-value)
(defmethod choice-item-selected-p ((button button))
(with-slots (selected) button
(= (abs selected) 2)))
(defmethod (setf choice-item-selected-p) (new-value (button button))
(declare (values new-value))
(let ((new-value (when new-value t)))
(unless (eq new-value (choice-item-selected-p button))
(with-slots (selected) button
(setf selected (if new-value 2 1))
(setf (choice-item-highlight-selected-p button) new-value)
(apply-callback button (if new-value :on :off)))))
new-value)
;;; =================================================================================== ;;;
;;; ;;;
;;; U t i l i t y F u n c t i o n s F o r A l l B u t t o n s ;;;
;;; ;;;
;;; =================================================================================== ;;;
(DEFEVENT button
(:button-press :button-1)
press-select)
(DEFEVENT button
(:button-release :button-1)
release-select)
(DEFMETHOD redisplay-button ((button button) &optional completely-p)
(with-slots (last-displayed-as) button
(CASE last-displayed-as
(:unhighlighted (display-button-unhighlighted button completely-p))
(:highlighted (display-button-highlighted button completely-p)))))
(DEFMETHOD display ((button button) &optional at-x at-y at-width at-height &key)
(DECLARE (IGNORE at-x at-y at-width at-height))
(WHEN (realized-p button)
;; Put self on the display afresh, completely redrawing everything...
(redisplay-button button t)))
;;;
;;; This function is used when a menu button must display the label of its menu's default
;;; choice, which may not fit within the menu button. It has more general usage than this,
;;; should be moved to utilities.lisp.
;;;
(DEFUN get-button-pixmaps (button)
;;
;; Look on the display's plist for an :OL-button-pixmaps property. If any action
;; button has created pixmaps from its images, they'll be here...
;;
(LET* ((scale (contact-scale button))
(display (contact-display button))
(button-pixmaps (GETF (display-plist display) :OL-button-pixmaps))
(button-pixmaps-for-this-size-button (GETF button-pixmaps scale))
(dims (GETF *button-dimensions-by-scale* scale)))
;;
;; If there are no pixmaps cached on the display's plist for this scale action button,
;; create some, put them into a button-pixmaps structure, then put it on the display's plist...
;;
(UNLESS button-pixmaps-for-this-size-button
(SETF button-pixmaps-for-this-size-button
(SETF (GETF button-pixmaps scale)
(make-button-pixmaps
:ab-button-ends-pixmap
(image-pixmap button (ab-button-ends-image dims))
:ab-clearing-stencil-pixmap
(image-pixmap button (ab-clearing-stencil-image dims))
:ab-default-ring-pixmap
(image-pixmap button (ab-default-ring-image dims))
:ab-body-clearing-stencil-pixmap
(image-pixmap button (ab-body-clearing-stencil-image dims))
:ai-default-ring-pixmap
(image-pixmap button (ai-default-ring-image dims))
:ai-body-clearing-stencil-pixmap
(image-pixmap button (ai-body-clearing-stencil-image dims))
:horizontal-menu-mark-pixmap
(image-pixmap button (ab-horizontal-menu-mark-image dims))
:vertical-menu-mark-pixmap
(image-pixmap button (ab-vertical-menu-mark-image dims)))))
(SETF (GETF (display-plist display) :OL-button-pixmaps) button-pixmaps))
;;
;; Return the button-pixmaps structure containing the pixmaps for this button's scale...
;;
button-pixmaps-for-this-size-button))
;;;----------------------------------------------------------------------------+
;;; |
;;; Toggle Button |
;;; |
;;;----------------------------------------------------------------------------+
(defcontact toggle-button (button)
((pointer-pressed :type boolean
:initform nil))
(:resources
(border-width :initform 0)
(switch :type (member :on :off)
:initform :off)))
(defmethod toggle-button-release-menu ((self toggle-button))
(declare (type toggle-button self))
(with-slots (pointer-pressed)
self
(when pointer-pressed
(choice-item-release self)
(setq pointer-pressed nil))))
(defmethod toggle-button-leave-with-menu-pressed ((self toggle-button))
(declare (type toggle-button self))
(with-slots (pointer-pressed)
self
(with-event (mode)
(when (and pointer-pressed
(eq mode :normal))
(choice-item-leave self)
(setq pointer-pressed nil)))))
(DEFMETHOD toggle-button-enter-with-menu-pressed ((self toggle-button))
(with-event (x y state)
(when (and (inside-contact-p self x y)
(NOT (ZEROP (LOGAND #.(make-state-mask :button-3) state))))
;; The pointer has been dragged over this button w/menu button
;; pressed. This has the same side effects as pressing the
;; select button so we go ahead and use the press procedure
;; to take care of visuals and approve the transition.
(when (choice-item-press self)
;; Transition was approved and button is now highlighted.
;; We set a flag so :button-release and :leave-notify events
;; will be handled.
(with-slots (pointer-pressed) self
(setq pointer-pressed t))))))
(DEFEVENT toggle-button
:enter-notify
toggle-button-enter-with-menu-pressed)
(defevent toggle-button
:leave-notify
toggle-button-leave-with-menu-pressed)
(defevent toggle-button
(:button-release :button-1)
tb-maybe-release-select)
;; These two translations are for Open Look menus, which allow item selection
;; on both button-1 and button-3 presses.
(DEFEVENT toggle-button
(:button-press :button-3)
press-select)
(DEFEVENT toggle-button
(:button-release :button-3)
toggle-button-release-menu)
(defmethod tb-maybe-release-select ((button toggle-button))
(with-slots (pointer-pressed)
button ;(the toggle-button button)
(when pointer-pressed
(release-select button))))
(defun make-toggle-button (&rest initargs)
(apply #'make-contact 'toggle-button initargs))
(defmethod initialize-instance :after ((toggle-button toggle-button)
&key switch &allow-other-keys)
(with-slots (selected) toggle-button
(when (eq switch :on)
(setf selected 2)
(display-button-highlighted toggle-button))))
;;; ========================================================================== ;;;
;;; ;;;
;;; ( T o g g l e ) B u t t o n P r o t o c o l M e t h o d s ;;;
;;; ;;;
;;; ========================================================================== ;;;
(defmethod button-switch ((toggle-button toggle-button))
(with-slots (selected) toggle-button
(if (= 1 (abs selected)) :off :on)))
(DEFMETHOD (SETF button-switch) (new-state (toggle-button toggle-button))
(ASSERT (member new-state '( :on :off)) nil
"~a is an illegal button state. Must be :ON or :OFF." new-state)
(LET ((current-state (button-switch toggle-button)))
(WHEN (NOT (EQ current-state new-state))
;; We simulate a button press and release to implement identical
;; semantics whether done via API or via gesture.
(WHEN (choice-item-press toggle-button)
;; When toggle press succeeded we follow it
;; with a release.
(choice-item-release toggle-button)))
(button-switch toggle-button)))
(DEFMETHOD leave ((toggle-button toggle-button))
(with-event (state mode)
(when (eq mode :normal)
(with-slots (selected pointer-pressed) toggle-button
(WHEN (AND (< selected 0)
(NOT (ZEROP (LOGAND (make-state-mask :button-1) state))))
(choice-item-leave toggle-button)
(setq pointer-pressed nil))))))
(DEFMETHOD preferred-size ((toggle-button toggle-button) &key width height border-width)
(declare (ignore width height border-width))
(DECLARE (VALUES preferred-width preferred-height
preferred-border-width))
;; A toggle-button must draw its border within its window so it can be dimmed if the button
;; becomes insensitive. So its border-width is zero.
;; Its preferred height is that dictated by its scale slot.
;; Its preferred width is the width of its label plus the right/left margins plus the border
;; width.
(with-slots (label font preferred-width) toggle-button
(LET*
((scale (contact-scale toggle-button))
(dims (GETF *button-dimensions-by-scale* scale))
p-width)
;; Since an Action Button's min-right-margin is 2 more than the interior margin a
;; Toggle Button should have, and since the border width of a Toggle Button is 1, we can
;; just use the Action Button's min-right-margin...
(SETF p-width (OR preferred-width
(SETF preferred-width
(+ (label-width toggle-button label)
(tb-min-right-margin dims) (tb-min-right-margin dims)))))
;; Since an Action Button's height is exactly that of a Toggle Button...
(VALUES p-width
(ab-height dims)
0))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Choice Item Protocol |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod choice-item-press ((toggle-button toggle-button))
(with-slots (selected) toggle-button
(let ((to-selected-p (= selected 1)))
(when (apply-callback-else (toggle-button :change-allowed-p to-selected-p) t)
(setf selected (- selected))
(if to-selected-p
(display-button-highlighted toggle-button)
(display-button-unhighlighted toggle-button))
(apply-callback toggle-button :changing to-selected-p)
t))))
(defmethod choice-item-release ((toggle-button toggle-button))
(with-slots (selected) toggle-button
(apply-callback toggle-button (IF (= 2 (SETF selected (+ 3 selected))) :on :off))))
(DEFMETHOD choice-item-leave ((toggle-button toggle-button))
(with-slots (selected) toggle-button
(IF (= 2 (SETF selected (- selected)))
(PROGN
(display-button-highlighted toggle-button)
(apply-callback toggle-button :canceling-change NIL))
(PROGN
(display-button-unhighlighted toggle-button)
(apply-callback toggle-button :canceling-change T)))))
(DEFMETHOD press-select ((toggle-button toggle-button))
(WHEN (choice-item-press toggle-button)
(with-slots (pointer-pressed) toggle-button
(setq pointer-pressed t))))
(DEFMETHOD release-select ((toggle-button toggle-button))
(with-event (state)
(with-slots (selected pointer-pressed) toggle-button
(WHEN (> 0 selected)
(UNWIND-PROTECT
(choice-item-release toggle-button)
(setq pointer-pressed nil))))))
(DEFMETHOD (SETF choice-item-selected-p) (new-value (toggle-button toggle-button))
;; Identical to (SETF button-switch) except returns boolean on/off indicator.
(DECLARE (VALUES new-value))
(EQ (SETF (button-switch toggle-button) (if new-value :on :off)) :on))
;;; =================================================================================== ;;;
;;; ;;;
;;; The Two Ways to Display a Toggle Button... ;;;
;;; ;;;
;;; =================================================================================== ;;;
(DEFmethod display-toggle-button ((toggle-button toggle-button) mode &optional completely-p)
(declare (type toggle-button toggle-button))
(with-slots (font fill-color foreground highlight-default-p width height)
toggle-button
(WHEN (realized-p toggle-button)
(LET ((tb-foreground foreground) (tb-fill-color fill-color) (tb-font font)
(tb-width width) (tb-height height)
stroke-width two-stroke-widths four-stroke-widths
(sensitive-p (sensitive-p toggle-button)))
(SETF stroke-width 1
two-stroke-widths (* 2 stroke-width)
four-stroke-widths (* 2 two-stroke-widths))
(using-gcontext (gc
:drawable toggle-button
:foreground tb-foreground
:background tb-fill-color
:font tb-font
:line-width stroke-width
:fill-style (IF sensitive-p :solid :stippled)
:stipple (UNLESS sensitive-p
(contact-image-mask toggle-button 50%gray :depth 1)))
(WHEN completely-p
(clear-area toggle-button
:x 0
:y 0
:width tb-width
:height tb-height)
;; Draw our rectangular OL UI border...
(DOTIMES (i stroke-width)
(draw-rectangle toggle-button gc i i
(- tb-width 1 i i)
(- tb-height 1 i i)))
(display-button-label toggle-button gc))
;; Draw/erase the highlight indicator...
(flet
((draw/erase-default-indicator ()
(DOTIMES (i stroke-width)
(draw-rectangle toggle-button gc (+ two-stroke-widths i) (+ two-stroke-widths i)
(- tb-width four-stroke-widths 1 i i)
(- tb-height four-stroke-widths 1 i i))))
(draw/erase-highlight ()
(DOTIMES (i stroke-width)
(draw-rectangle toggle-button gc (+ stroke-width i) (+ stroke-width i)
(- tb-width two-stroke-widths 1 i i)
(- tb-height two-stroke-widths 1 i i))))
)
#+ansi-common-lisp (declare (inline draw/erase-default-indicator draw/erase-highlight))
;; Draw the default indicator if necessary...
(if highlight-default-p
(draw/erase-default-indicator)
(with-gcontext (gc :foreground tb-fill-color :background tb-foreground)
(draw/erase-default-indicator)))
(IF (EQ mode :unhighlighted)
(with-gcontext (gc :foreground tb-fill-color :background tb-foreground)
(draw/erase-highlight))
(draw/erase-highlight)))
)))))
(DEFMETHOD display-button-highlighted ((toggle-button toggle-button) &optional completely-p)
(with-slots (last-displayed-as) toggle-button
(display-toggle-button toggle-button :highlighted completely-p)
(SETF last-displayed-as :highlighted)))
(DEFMETHOD display-button-unhighlighted ((toggle-button toggle-button) &optional completely-p)
(with-slots (last-displayed-as) toggle-button
(display-toggle-button toggle-button :unhighlighted completely-p)
(SETF last-displayed-as :unhighlighted)))
;;; =================================================================================== ;;;
;;; ;;;
;;; D i s p l a y a T o g g l e B u t t o n ' s L a b e l ;;;
;;; ;;;
;;; =================================================================================== ;;;
(DEFMETHOD display-button-label ((self toggle-button) gc)
(display-any-buttons-label self gc 1 -2))
(defgeneric label-width (button label)
(:documentation "Return the width of the button LABEL in pixels."))
(defmethod label-width ((button button) (label string))
(with-slots (font) button
(text-width font label)))
(defmethod label-width ((button button) (label pixmap))
(or (getf (pixmap-plist label) :width)
(with-state (label)
(setf (getf (pixmap-plist label) :width) (drawable-width label)
(getf (pixmap-plist label) :height) (drawable-height label)))))
(defmethod display-any-buttons-label ((button button) gc top-border-thickness left-border-adjustment)
(with-slots (label label-alignment width height)
button ; (the button button)
(let*
((dims (getf *button-dimensions-by-scale* (contact-scale button)))
(label-width (label-width button label))
(margin (- (ab-left-button-end-width dims) left-border-adjustment))
(left-margin (max margin
(case label-alignment
(:left 0)
(:center (pixel-round (- width label-width) 2))
(:right (- width margin label-width))))))
(if (stringp label)
(draw-glyphs
button gc
left-margin (+ top-border-thickness (ab-text-baseline dims))
label)
;; Else display pixmap label...
(let ((label-height (getf (pixmap-plist label) :height)))
(with-gcontext (gc :fill-style :tiled :tile label)
(draw-rectangle
button gc
left-margin (max 0 (pixel-round (- height label-height) 2))
label-width label-height t)))))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Action Button |
;;; |
;;;----------------------------------------------------------------------------+
(defcontact action-button (button) ()
(:resources (border-width :initform 0)))
(defun make-action-button (&rest initargs)
(apply #'make-contact 'action-button initargs))
(defcontact action-item (action-button) ()
(:resources
(label-alignment :initform :left)))
(DEFUN circular-list-of-one-item (item)
"Return a circular list whose elements are ITEM (over and over again)."
(LET ((tem (LIST item)))
(RPLACD tem tem)
TEM))
(defmethod choice-item-press ((action-button action-button))
;; choice-item-press does the necessary tasks to reflect
;; an action-button press provided that the :change-allowed-p
;; callback (if any) allows the state change. The returned
;; value indicates whether the press was allowed or not.
(when (apply-callback-else (action-button :change-allowed-p t) t)
(display-button-highlighted action-button)
(apply-callback action-button :press)
(apply-callback action-button :changing t)
t))
(DEFMETHOD choice-item-release ((action-button action-button))
;; choice-item-release does the necessary tasks to reflect
;; an action-button release. It is assumed that a press has
;; occurred and that the press action was allowed; thus, we
;; don't invoke the :change-allowed-p callback again here.
(with-slots (selected) action-button
(display-action-button-busy action-button)
(display-force-output (contact-display action-button))
;; Ensure highlight is cleaned up in case :release callback performs a throw.
(unwind-protect
(apply-callback action-button :release)
(SETF selected 2)
(apply-callback action-button :on)
(SETF selected 1)
(display-button-unhighlighted action-button)
(apply-callback action-button :changing NIL)
(apply-callback action-button :off))))
(DEFMETHOD choice-item-leave ((action-button action-button))
(display-button-unhighlighted action-button)
(apply-callback action-button :canceling-change T))
(DEFMETHOD press-select ((action-button action-button))
(with-event (x y)
(WHEN (inside-contact-p action-button x y)
;; Choice-item-press will set last-displayed-as if the
;; transition is allowed.
(choice-item-press action-button))))
(DEFMETHOD release-select ((action-button action-button))
(with-slots (last-displayed-as) action-button
;; Do nothing unless highlighted/selected already...
(WHEN (EQ last-displayed-as :highlighted)
(choice-item-release action-button))))
(DEFMETHOD (SETF choice-item-selected-p) (new-value (action-button action-button))
(DECLARE (VALUES new-value))
(with-slots (last-displayed-as) action-button
;; For an unselected action button and a new-value of T, this method must act like a button
;; press followed immediately by a button release. If the button is already
;; selected, this method does nothing. Note that to prevent strange behavior if the
;; application calls us with a new-value of T from within the action-button's :release
;; callback, we do not check the button's selected-p slot. Instead, we check the button's
;; last-displayed-as slot, only doing something if the button is completely inactive.
(WHEN (and new-value (EQ last-displayed-as :unhighlighted))
(WHEN (choice-item-press action-button)
;; When press action was allowed we proceed with
;; ersatz release.
(choice-item-release action-button)))
;; else the application is trying to unselect an action button. This is meaningful only when
;; the action button is selected, which is a momentary state for an action button. A
;; "selected" action button by definition is in the process of transitioning to "unselected".
;; As a part of this transition all callbacks will be applied. So in this case it seems
;; reasonable for the method to do nothing
new-value))
(DEFMETHOD leave ((action-button action-button))
(with-event (state mode)
(when (eq mode :normal)
(with-slots (last-displayed-as) action-button
(WHEN (AND (EQ last-displayed-as :highlighted)
(NOT (ZEROP (LOGAND (make-state-mask :button-1) state))))
(choice-item-leave action-button))))))
(defevent action-button
:leave-notify
leave)
;;;
;;; The three basic ways to display an action button...
;;;
(DEFMETHOD redisplay-button ((action-button action-button) &optional completely-p)
(with-slots (last-displayed-as) action-button
(CASE last-displayed-as
(:unhighlighted (display-button-unhighlighted action-button completely-p))
(:highlighted (display-button-highlighted action-button completely-p))
(:busy (display-action-button-busy action-button completely-p)))))
(DEFMETHOD display-button-unhighlighted ((action-button action-button) &optional completely-p)
(with-slots (font fill-color foreground highlight-default-p last-displayed-as) action-button
(when (realized-p action-button)
(LET ((ab-foreground foreground) (ab-fill-color fill-color) (ab-font font)
(sensitive-p (sensitive-p action-button)))
;; If displaying a dimmed (insensitive) button, always redraw the entire thing...
(UNLESS sensitive-p
(SETF completely-p t))
(using-gcontext (gc
:drawable action-button
:foreground ab-foreground
:background ab-fill-color
:font ab-font
:fill-style (IF sensitive-p :solid :stippled)
:stipple (UNLESS sensitive-p
(contact-image-mask action-button 50%gray :depth 1)))
(with-gcontext (gc :foreground ab-fill-color :background ab-foreground)
(IF completely-p
(clear-button-and-display-border action-button gc)
(just-clear-body-of-button action-button gc)))
(display-button-label action-button gc)
(WHEN highlight-default-p
(display-default-indicator action-button gc)))))
(SETF last-displayed-as :unhighlighted)))
(DEFMETHOD display-button-highlighted ((action-button action-button) &optional completely-p)
(with-slots (font fill-color foreground last-displayed-as) action-button
(when (realized-p action-button)
(LET ((ab-foreground foreground) (ab-fill-color fill-color) (ab-font font))
;; An insensitive action button can never be busy, so sensitive-p is not checked
;; or handled here...
(using-gcontext (gc
:drawable action-button
:foreground ab-fill-color
:background ab-foreground
:font ab-font)
(with-gcontext (gc :foreground ab-foreground :background ab-fill-color)
(IF completely-p
(clear-button-and-display-border action-button gc)
(just-clear-body-of-button action-button gc)))
(display-button-label action-button gc))))
(SETF last-displayed-as :highlighted)))
(defmethod display-action-button-busy ((action-button action-button) &optional completely-p)
(with-slots (font fill-color foreground last-displayed-as) action-button
(when (realized-p action-button)
(let ((ab-foreground foreground) (ab-fill-color fill-color) (ab-font font))
;; An insensitive action button can never be busy, so sensitive-p is not checked
;; or handled here...
;; Clear out the non-margin, non-border part of the button with the busy-pixmap
;; stipple pattern...
(using-gcontext
(gc
:drawable action-button
:foreground ab-fill-color
:background ab-foreground
:stipple (contact-image-mask action-button 88%gray :depth 1)
:fill-style :opaque-stippled)
(if completely-p
(clear-button-and-display-border action-button gc)
(just-clear-body-of-button action-button gc)))
;; Draw the text label in the foreground color...
(using-gcontext
(gc
:drawable action-button
:foreground ab-foreground
:background ab-fill-color
:font ab-font)
(display-button-label action-button gc))))
(setf last-displayed-as :busy)))
(DEFMETHOD display-default-indicator ((action-button action-button) gc)
;; Draws the 1-2 pixel wide default indicator in the foreground color of GC...
(with-slots (width height) action-button
(LET* ((scale (contact-scale action-button)) interior-width
(dims (GETF *button-dimensions-by-scale* scale))
(top-border-thickness (IF (TYPEP action-button 'action-item) 0 1))
(button-pixmaps (get-button-pixmaps action-button)))
(SETF interior-width
(- width (ab-left-button-end-width dims) (ab-right-button-end-width dims)))
;; Draw the left-end of the default indicator...
(with-gcontext (gc :clip-x 0 :clip-y top-border-thickness
:clip-mask (ab-default-ring-pixmap button-pixmaps))
(draw-rectangle action-button gc
0 top-border-thickness
(ab-left-button-end-width dims) height t))
;; Draw the top horizontal line of the default indicator...
(draw-rectangle action-button gc
(ab-left-button-end-width dims)
(+ top-border-thickness 1)
interior-width 0)
;; Draw the bttom horizontal line of the default indicator...
(draw-rectangle action-button gc
(ab-left-button-end-width dims)
(+ top-border-thickness (ab-default-ring-height dims))
interior-width 0)
;; Draw the right-end of the default indicator...
(with-gcontext (gc :clip-x interior-width :clip-y top-border-thickness
:clip-mask (ab-default-ring-pixmap button-pixmaps))
(draw-rectangle action-button gc
(+ (ab-left-button-end-width dims) interior-width)
top-border-thickness
(ab-right-button-end-width dims) (contact-height action-button) t)))))
(DEFMETHOD just-clear-body-of-button ((action-button action-button) gc)
(with-slots (width height) action-button
(LET* ((scale (contact-scale action-button))
interior-width body-clear-stencil
(dims (GETF *button-dimensions-by-scale* scale))
(top-border-thickness (IF (TYPEP action-button 'action-item) 0 1))
(button-pixmaps (get-button-pixmaps action-button))
(fill-style (gcontext-fill-style gc)))
(SETF interior-width
(- width (ab-left-button-end-width dims) (ab-right-button-end-width dims)))
(when (< interior-width 0)
(setq interior-width 0))
(SETF body-clear-stencil (ab-body-clearing-stencil-pixmap button-pixmaps))
(with-gcontext (gc :fill-style (IF (EQ fill-style :stippled) :solid fill-style))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Clear out the button's non-border, non-margin pixels to
;; the foreground color of GC...
;; Start by clearing the left end of the button...
(with-gcontext (gc :clip-x 0 :clip-y top-border-thickness
:clip-mask body-clear-stencil)
(draw-rectangle action-button gc 0 top-border-thickness
(ab-left-button-end-width dims) (contact-height action-button) t))
;; Clear out the background for the label...
(draw-rectangle action-button gc
(ab-left-button-end-width dims)
(+ top-border-thickness 1)
interior-width (ab-default-ring-height dims) t)
;; Clear out the drawable pixels of the right button end...
(with-gcontext (gc :clip-x interior-width
:clip-y top-border-thickness
:clip-mask body-clear-stencil)
(draw-rectangle action-button gc
(+ (ab-left-button-end-width dims) interior-width)
top-border-thickness
(ab-right-button-end-width dims) height t))))))
(DEFMETHOD clear-button-and-display-border ((action-button action-button) gc)
(with-slots (foreground fill-color width height) action-button
(LET* ((scale (contact-scale action-button))
(ab-fill-color fill-color) (ab-foreground foreground) interior-width
clear-stencil border-stencil
(dims (GETF *button-dimensions-by-scale* scale))
(button-pixmaps (get-button-pixmaps action-button))
(fill-style (gcontext-fill-style gc)))
(SETF interior-width
(- width (ab-left-button-end-width dims) (ab-right-button-end-width dims)))
(when (< interior-width 0)
(setq interior-width 0))
(SETF clear-stencil (ab-clearing-stencil-pixmap button-pixmaps)
border-stencil (ab-button-ends-pixmap button-pixmaps))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Clear out all the button's pixels to fill-color...
(with-gcontext (gc :fill-style (IF (EQ fill-style :stippled) :solid fill-style))
;; Start by clearing the left end of the button...
(with-gcontext (gc :clip-x 0 :clip-y 0 :clip-mask clear-stencil)
(draw-rectangle action-button gc 0 0
(ab-left-button-end-width dims) height t))
;; Clear out the background for the label...
(draw-rectangle action-button gc
(ab-left-button-end-width dims) 0
interior-width height t)
;; Clear out the drawable pixels of the right button end...
(with-gcontext (gc :clip-x interior-width :clip-y 0 :clip-mask clear-stencil)
(draw-rectangle action-button gc
(+ (ab-left-button-end-width dims) interior-width) 0
(ab-right-button-end-width dims) height t)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Draw in the button's border in foreground...
(with-gcontext (gc :foreground ab-foreground :background ab-fill-color
:fill-style (IF (EQ fill-style :opaque-stippled) :solid fill-style))
;; Start by drawing the border on the left end of the button...
(with-gcontext (gc :clip-x 0 :clip-y 0 :clip-mask border-stencil)
(draw-rectangle action-button gc 0 0
(ab-left-button-end-width dims)
height t))
;; Draw the top and bottom borders for the label...
(draw-rectangle action-button gc
(ab-left-button-end-width dims) 0 interior-width 0)
(draw-rectangle action-button gc
(ab-left-button-end-width dims) (- (ab-height dims) 2)
interior-width 1)
;; Finish by drawing the border on the right end of the button...
(with-gcontext (gc :clip-x interior-width :clip-y 0 :clip-mask border-stencil)
(draw-rectangle action-button gc
(+ (ab-left-button-end-width dims) interior-width) 0
(ab-right-button-end-width dims) height t)))
)))
(DEFMETHOD display-button-label ((self action-button) gc)
(display-any-buttons-label self gc 1 0))
(DEFMETHOD preferred-size ((action-button action-button) &key width height border-width)
(declare (ignore width height border-width))
(DECLARE (VALUES preferred-width preferred-height
preferred-border-width))
;; An action button always wants a border-width of zero.
;; Its preferred height is that dictated by its scale slot.
;; Given a text label, its preferred width is the width of its label in the font
;; corresponding to the scale plus the widths of its button ends.
;; Given an image or pixmap label, use its width.
(with-slots (label font preferred-width width height) action-button
(LET* ((scale (contact-scale action-button))
(dims (GETF *button-dimensions-by-scale* scale))
p-width p-height)
(SETF p-width
(OR preferred-width
(SETF preferred-width (+ (ab-left-button-end-width dims)
(ab-right-button-end-width dims)
(label-width action-button label)))))
(SETF p-height (ab-height dims))
(VALUES p-width (ab-height dims) 0))))
(DEFMETHOD inside-contact-p ((action-button action-button) x y)
"Returns T iff the point (X,Y) is within the rounded borders of ACTION-BUTTON."
(with-slots (width height) action-button
(LET* ((scale (contact-scale action-button))
(dims (GETF *button-dimensions-by-scale* scale)))
(AND (< -1 x width) (< -1 y height)
(OR (<= (ab-left-button-end-width dims)
x
(- width (ab-right-button-end-width dims) 1))
(LET* ((clearing-stencil-array (ab-clearing-stencil-array dims)))
(WHEN (> x (ab-left-button-end-width dims))
(DECF x (- width (ab-left-button-end-width dims) (ab-right-button-end-width dims))))
(NOT (ZEROP (AREF clearing-stencil-array x y)))))))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Action Item |
;;; |
;;;----------------------------------------------------------------------------+
;;; An ACTION-ITEM is a specialization of an ACTION-BUTTON and is intended for use
;;; in OL compliant menus. It differs from an ACTION-BUTTON in appearance as well
;;; as in its sensitivity to various mouse gestures depending on the mode of the
;;; menu which contains it.
#|| ; moved fordward in this file
(defcontact action-item (action-button) ()
(:resources
(label-alignment :initform :left)))
||#
(defmethod action-item-release-menu ((self action-item))
(declare (type action-item self))
(with-slots (last-displayed-as)
self
(when (eq last-displayed-as :highlighted)
(choice-item-release self))))
(defmethod action-item-leave-with-menu-pressed ((self action-item))
(declare (type action-item self))
(with-slots (last-displayed-as) self
(with-event (mode)
(unless (eq mode :grab)
(when (eq last-displayed-as :highlighted)
(choice-item-leave self))))))
(defmethod action-item-enter-with-menu-pressed ((self action-item))
(with-slots (last-displayed-as) self
(when (eq last-displayed-as :unhighlighted)
(with-event (x y state)
(when (and (inside-contact-p self x y)
(not (zerop (logand #.(make-state-mask :button-3) state))))
;; The pointer has been dragged over this button w/menu button
;; pressed. This has the same side effects as pressing the
;; select button so we go ahead and use the press procedure
;; to take care of visuals and approve the transition.
;; The last-displayed-as slot is set inside choice-item-press
;; if the transition is approved.
(choice-item-press self))))))
(DEFEVENT action-item
:enter-notify
action-item-enter-with-menu-pressed)
(defevent action-item
:leave-notify
action-item-leave-with-menu-pressed)
(defevent action-item
(:button-release :button-3)
action-item-release-menu)
;; This translation is for Open Look menus, which allow item selection
;; on both button-1 and button-3 presses.
(DEFEVENT action-item
(:button-press :button-3)
press-select)
(defun make-action-item (&rest initargs)
(apply #'make-contact 'action-item initargs))
;;;
;;; New drawing methods for an action-item...
;;;
(DEFMETHOD inside-contact-p ((self action-item) x y)
"Returns T iff the point (X,Y) is within an action-item."
(with-slots (width height) self
(AND (< -1 x width) (< -1 y height))))
(defmethod display-button-label ((self action-item) gc)
(with-slots (label label-alignment font width height) self
(let*
((label-width (label-width self label))
(dims (getf *button-dimensions-by-scale* (contact-scale self)))
(left-margin (max (ai-button-end-width dims)
(case label-alignment
(:left 0)
(:center (pixel-round (- width label-width) 2))
(:right (- width (ai-button-end-width dims) label-width))))))
(if (stringp label)
(draw-glyphs self gc left-margin (ai-text-baseline dims) label)
;; Else draw pixmap label...
(let ((label-height (getf (pixmap-plist label) :height)))
(with-gcontext (gc :fill-style :tiled :tile label)
(draw-rectangle
self gc
left-margin (max 0 (pixel-round (- height label-height) 2))
label-width label-height t)))))))
(DEFMETHOD display-default-indicator ((action-item action-item) gc)
;; Draws the 1-2 pixel wide default indicator in the foreground color of GC...
(with-slots (width height) action-item
(LET* ((scale (contact-scale action-item)) interior-width
(dims (GETF *button-dimensions-by-scale* scale))
(button-pixmaps (get-button-pixmaps action-item))
(button-end-width (ai-button-end-width dims))
(default-ring-height (ai-default-ring-height dims)))
(SETF interior-width
(- width button-end-width button-end-width))
;; Draw the left-end of the default indicator...
(with-gcontext (gc :clip-x 0 :clip-y 0
:clip-mask (ai-default-ring-pixmap button-pixmaps))
(draw-rectangle action-item gc
0 0
button-end-width default-ring-height t))
;; Draw the top horizontal line of the default indicator...
(draw-rectangle action-item gc button-end-width 0 interior-width 0)
;; Draw the bottom horizontal line of the default indicator...
(draw-rectangle action-item gc button-end-width (1- default-ring-height) interior-width 0)
;; Draw the right-end of the default indicator...
(with-gcontext (gc :clip-x interior-width :clip-y 0
:clip-mask (ai-default-ring-pixmap button-pixmaps))
(draw-rectangle action-item gc
(+ button-end-width interior-width) 0
button-end-width default-ring-height t)))))
(DEFMETHOD just-clear-body-of-button ((action-item action-item) gc)
(with-slots (width height) action-item
(LET* ((scale (contact-scale action-item)) interior-width body-clear-stencil
(dims (GETF *button-dimensions-by-scale* scale))
(button-pixmaps (get-button-pixmaps action-item))
(button-end-width (ai-button-end-width dims))
(default-ring-height (ai-default-ring-height dims))
(fill-style (gcontext-fill-style gc)))
(SETF interior-width
(- width button-end-width button-end-width))
(when (< interior-width 0)
(setq interior-width 0))
(SETF body-clear-stencil (ai-body-clearing-stencil-pixmap button-pixmaps))
(with-gcontext (gc :fill-style (IF (EQ fill-style :stippled) :solid fill-style))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Clear out the button's non-border, non-margin pixels to
;; the foreground color of GC...
;; Start by clearing the left end of the button...
(with-gcontext (gc :clip-x 0 :clip-y 0 :clip-mask body-clear-stencil)
(draw-rectangle action-item gc 0 0
button-end-width default-ring-height t))
;; Clear out the background for the label...
(draw-rectangle action-item gc
button-end-width 0
interior-width default-ring-height t)
;; Clear out the drawable pixels of the right button end...
(with-gcontext (gc :clip-x interior-width :clip-y 0 :clip-mask body-clear-stencil)
(draw-rectangle action-item gc
(+ button-end-width interior-width) 0
button-end-width default-ring-height t))))))
(DEFMETHOD clear-button-and-display-border ((action-item action-item) gc)
(with-slots (foreground fill-color width height) action-item
(clear-area action-item)
(just-clear-body-of-button action-item gc)))
(DEFMETHOD preferred-size ((action-item action-item) &key width height border-width)
(declare (ignore width height border-width))
(DECLARE (VALUES preferred-width preferred-height
preferred-border-width))
;; An action button always wants a border-width of zero.
;; Its preferred height is that dictated by its scale slot.
;; Given a text label, its preferred width is the width of its label in the font
;; corresponding to the scale plus the widths of its button ends.
;; Given an image or pixmap label, use its width.
(with-slots (label font preferred-width width height) action-item
(LET* ((scale (contact-scale action-item))
(dims (GETF *button-dimensions-by-scale* scale))
(button-end-width (ai-button-end-width dims))
p-width)
(SETF p-width
(OR preferred-width
(SETF preferred-width (+ button-end-width
(label-width action-item label)
button-end-width))))
(VALUES p-width (ai-height dims) 0))))